package com.ajavaer.framework.handle.impl;

import com.ajavaer.framework.cache.type.CacheItem;
import com.ajavaer.framework.common.spring.SpringContext;
import com.ajavaer.framework.config.CacheEngineConfig;
import com.ajavaer.framework.handle.RedisHandle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.util.StringUtils;
import redis.clients.jedis.*;

import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * redis句柄
 * <p>
 * 如果spring容器中已经有JedisPool,JedisCluster 直接使用容器中的连接
 * </p>
 */
public class RedisHandleImpl implements RedisHandle {

    private Logger log = LoggerFactory.getLogger(RedisHandleImpl.class);

    private JedisPool jedisPool;
    private JedisCluster jedisCluster;
    private Integer retryCount = 0;
    private CacheEngineConfig cacheEngineConfig;
    private ApplicationContext contextAware;

    @Override
    public void init(CacheEngineConfig cacheEngineConfig, ApplicationContext contextAware) {
        this.cacheEngineConfig = cacheEngineConfig;
        this.contextAware = contextAware;
        if (jedisPool != null) {
            jedisPool.close();
            jedisPool.destroy();
            jedisPool = null;
        }
        if (contextAware != null && !SpringContext.ready()) {
            SpringContext.setContext(contextAware);
        }
        switch (cacheEngineConfig.getMode()) {
            case singleton:
                jedisPool = SpringContext.get(JedisPool.class, p -> new JedisPool(SpringContext.get(JedisPoolConfig.class, j -> jedisPoolConfig()), cacheEngineConfig.getHost(), cacheEngineConfig.getPort(), 10000, cacheEngineConfig.getPassword()));
                break;
            case cluster:
                jedisCluster = SpringContext.get(JedisCluster.class, p -> {
                    String[] serverArray = this.cacheEngineConfig.getHost().split(",");
                    Set<HostAndPort> nodes = new HashSet<>();
                    for (String ipPort : serverArray) {
                        String[] ipPortPair = ipPort.split(":");
                        nodes.add(new HostAndPort(ipPortPair[0].trim(), Integer.valueOf(ipPortPair[1].trim())));
                    }
                    return new JedisCluster(nodes, 3000, 1000, 1, StringUtils.isEmpty(cacheEngineConfig.getPassword()) ? null : cacheEngineConfig.getPassword(),
                            SpringContext.get(JedisPoolConfig.class, j -> jedisPoolConfig()));
                });
                break;

        }

    }

    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxIdle(2000);
        config.setTestOnBorrow(true);
        config.setTestOnReturn(true);
        config.setMaxTotal(-1);
        config.setMaxWaitMillis(10000);
        return config;
    }

    @Override
    public void put(String key, String value, Integer expire, TimeUnit timeUnit) {
        JedisCommands jedis = null;
        try {
            jedis = commands();
            jedis.set(key, value);
            if (expire > 0) {
                jedis.expire(key, (int) timeUnit.toSeconds((long) expire));
            }
        } catch (Exception e) {
            exception(key, e);
        } finally {
            close(jedis);
        }
    }

    @Override
    public String get(String key, Function<String, CacheItem<String, String>> create) {
        JedisCommands jedis = null;
        try {
            jedis = commands();
            String value = jedis.get(key);
            if (value == null) {
                CacheItem<String, String> apply = create.apply(key);
                if (apply != null) {
                    if (apply.getExpire() != null && apply.getData() != null) {
                        jedis.set(key, apply.getData());
                        if (apply.getExpire() > 0) {
                            jedis.expire(key, (int) apply.getTimeUnit().toSeconds(apply.getExpire()));
                        }
                    }
                    return apply.getData();
                }
                return null;
            } else {
                return value;
            }
        } catch (Exception e) {
            exception(key, e);
        } finally {
            close(jedis);
        }
        return null;
    }

    @Override
    public void close(JedisCommands jedis) {
        if (jedis != null && jedisPool != null) {
            if (jedis instanceof Jedis) {
                ((Jedis) jedis).close();
            }

        }
    }

    private void exception(String key, Exception e) {
        log.error("redis exception,key:" + key + ",ex:" + e.getMessage(), e);
    }

    @Override
    public void destroy() {
        if (jedisPool != null) {
            jedisPool.close();
        }
    }

    public JedisCommands commands() {
        switch (cacheEngineConfig.getMode()) {
            case singleton:
                return jedis();
            case cluster:
                return cluster();
        }
        return null;
    }

    private JedisCommands cluster() {
        return jedisCluster;
    }

    /**
     * 获取redis连接,断线自动重连
     *
     * @return
     */
    @Override
    public Jedis jedis() {
        try {
            if (jedisPool != null) {
                Jedis jedis = jedisPool.getResource();
                retryCount = 0;
                return jedis;
            } else {
                Thread.sleep(1000);
                retryCount++;
                if (retryCount > 3) {
                    throw new Exception("");
                }
                return jedis();
            }
        } catch (Exception e) {
            if (retryCount > 3) {
                log.error("get redis has error, try again 3 times still fail");
                return null;
            }
            log.error("get redis has error :" + e.getMessage());
            retryCount++;
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
            init(cacheEngineConfig, contextAware);
            log.error("retry conn redis :" + retryCount);
            return jedis();
        }
    }

    @Override
    public void expire(String key, int time) {
        JedisCommands jedis = null;
        try {
            jedis = commands();
            jedis.expire(key, time);
        } catch (Exception e) {
            exception(key, e);
        } finally {
            close(jedis);
        }
    }

    @Override
    public void hincrBy(String key, String field, int count) {
        JedisCommands jedis = null;
        try {
            jedis = commands();
            jedis.hincrBy(key, field, count);
        } catch (Exception e) {
            exception(key, e);
        } finally {
            close(jedis);
        }
    }

    @Override
    public boolean exists(String key) {
        JedisCommands jedis = null;
        try {
            jedis = commands();
            return jedis.exists(key);
        } catch (Exception e) {
            exception(key, e);
        } finally {
            close(jedis);
        }
        return false;
    }

    @Override
    public void delete(String key) {
        del(key);
    }

    @Override
    public boolean hexists(String key, String field) {
        JedisCommands jedis = null;
        try {
            jedis = commands();
            return jedis.hexists(key, field);
        } catch (Exception e) {
            exception(key, e);
        } finally {
            close(jedis);
        }
        return false;
    }

    @Override
    public Long decr(String key) {
        JedisCommands jedis = null;
        try {
            jedis = commands();
            return jedis.decr(key);
        } catch (Exception e) {
            exception(key, e);
        } finally {
            close(jedis);
        }
        return -1l;
    }

    @Override
    public Long incr(String key) {
        JedisCommands jedis = null;
        try {
            jedis = commands();
            return jedis.incr(key);
        } catch (Exception e) {
            exception(key, e);
        } finally {
            close(jedis);
        }
        return 1l;
    }

    @Override
    public Long del(String key) {
        JedisCommands jedis = null;
        try {
            jedis = commands();
            return jedis.del(key);
        } catch (Exception e) {
            exception(key, e);
        } finally {
            close(jedis);
        }
        return 1l;
    }

    @Override
    public void del(byte[] key) {
        JedisCommands jedis = null;
        try {
            jedis = commands();
            ((Jedis) jedis).del(key);
        } finally {
            close(jedis);
        }
    }

    @Override
    public Set<byte[]> keys(String prefix) {
        Set<byte[]> keys = null;
        JedisCommands jedis = null;
        try {
            jedis = commands();
            byte[] pattern = prefix.getBytes(Charset.forName("UTF-8"));
            if (jedis instanceof Jedis) {
                keys = ((Jedis) jedis).keys(pattern);
            } else if (jedis instanceof JedisCluster) {
                keys = new TreeSet<>();
                JedisCluster jedisCluster = (JedisCluster) jedis;
                Map<String, JedisPool> clusterNodes = jedisCluster.getClusterNodes();
                for (String key : clusterNodes.keySet()) {
                    JedisPool jedisPool = clusterNodes.get(key);
                    Jedis jedisConn = jedisPool.getResource();
                    try {
                        keys.addAll(jedisConn.keys(pattern));
                    } catch (Exception e) {
                        log.error("Getting keys error: {}", e);
                    } finally {
                        jedisConn.close();
                    }
                }
            }
        } finally {
            close(jedis);
        }
        return keys;
    }

    @Override
    public void put(byte[] key, byte[] value) {
        JedisCommands jedis = null;
        try {
            jedis = commands();
            if (jedis instanceof Jedis) {
                ((Jedis) jedis).set(key, value);
            } else if (jedis instanceof JedisCluster) {
                ((JedisCluster) jedis).set(key, value);
            }
        } finally {
            close(jedis);
        }
    }

    @Override
    public byte[] getIfPresent(byte[] key) {
        JedisCommands jedis = null;
        try {
            jedis = commands();
            if (jedis instanceof Jedis) {
                return ((Jedis) jedis).get(key);
            } else if (jedis instanceof JedisCluster) {
                return ((JedisCluster) jedis).get(key);
            }
            return null;
        } finally {
            close(jedis);
        }
    }

    @Override
    public void hset(String key, String field, String value) {
        hset(key, field, value, -1);
    }

    @Override
    public void hset(String key, String field, String value, int expire) {
        JedisCommands jedis = null;
        try {
            jedis = commands();
            jedis.hset(key, field, value);
            if (expire != -1) {
                jedis.expire(key, expire);
            }
        } finally {
            close(jedis);
        }
    }

    @Override
    public String hget(String key, String field) {
        JedisCommands jedis = null;
        try {
            jedis = commands();
            return jedis.hget(key, field);
        } finally {
            close(jedis);
        }
    }

    @Override
    public Map<String, String> hgetAll(String key) {
        JedisCommands jedis = null;
        try {
            jedis = commands();
            return jedis.hgetAll(key);
        } finally {
            close(jedis);
        }
    }

    @Override
    public void hsetAll(String key, Map<String, String> value, int expire) {
        JedisCommands jedis = null;
        try {
            jedis = commands();
            jedis.del(key);
            for (Map.Entry<String, String> item : value.entrySet()) {
                jedis.hset(key, item.getKey(), item.getValue());
            }
            if (expire > 0) {
                jedis.expire(key, expire);
            }
        } finally {
            close(jedis);
        }
    }

    @Override
    public void hsetAll(String key, Map<String, String> value) {
        hsetAll(key, value, 0);
    }

    @Override
    public List<String> hvals(String key) {
        JedisCommands jedis = null;
        try {
            jedis = commands();
            return jedis.hvals(key);
        } finally {
            close(jedis);
        }
    }


}
